/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */

package sk.jp.commons.objcache;

import java.util.HashMap;
import java.util.Map;

/**
 * Simple objects cache.
 * <p>Thread safe.
 *
 * @author Jan Pastor 
 */
public class ObjectCache<T> {

    private final long timeout;
    private final Map<String,CacheItem<T>> cache;

    //------------------------------------------------------------------

    /**
     * Create new cache instance.
     * @param timeout cached objects timeout [millis].
     */
    public ObjectCache(long timeout) {
        this.timeout = timeout;
        cache = new HashMap<String, CacheItem<T>>();
    }

    //------------------------------------------------------------------

    /**
     * Test if cache contains specified item.
     * <p>Checks if cache contains specified item. For cached item checks if
     * item is not expired. Expired item is removed from cache.
     * @param id item ID.
     * @return true if cache contains specified item and (if contains) item is
     * not expired.
     */
    public synchronized boolean hasItem(String id) {
        if(id == null) throw new NullPointerException("Parameter id is null");
        if("".equals(id)) throw new IllegalArgumentException("Parameter id has empty value");
        //
        CacheItem item = cache.get(id);
        if(item == null){
            return false;
        }
        if(item.isExpired()){
            cache.remove(id);
            return false;
        }
        return true;
    }

    /**
     * Put item (object) to cache.
     * <p>If any item with equals ID is already cached, it is replaced.
     * @param id item ID. Null or empty string is not permited.
     * @param value item value. Null is not permited.
     * @noinspection CallToNativeMethodWhileLocked
     */
    public synchronized void putItem(String id, T value) {
        if(id == null) throw new NullPointerException("Parameter id is null");
        if("".equals(id)) throw new IllegalArgumentException("Parameter id has empty value");
        if(value == null) throw new NullPointerException("Parameter value is null");
        //
        cache.put(id, new CacheItem<T>(id, value, System.currentTimeMillis() + timeout) );
    }

    /**
     * Returns item from cache.
     * <p>If specified item isn't cached, returns null.
     * <p>Expired item is removed from cache.
     * @param id ID of item to get.
     * @return requested item (object) from cache, or null if item is not cached,
     * or if cached but expired.
     */
    public synchronized T getItem(String id) {
        if(id == null) throw new NullPointerException("Parameter id is null");
        if("".equals(id)) throw new IllegalArgumentException("Parameter id has empty value");
        //
        CacheItem<T> item = cache.get(id);
        if(item == null){
            return null;
        }
        if(item.isExpired()){
            cache.remove(id);
            return null;
        }
        return item.getValue();
    }

    /**
     * Removes specified item from cache.
     * @param id ID of item to remove.
     * @return true, if cache contains item before removing, false if not.
     */
    public synchronized boolean removeItem(String id) {
        if(id == null) throw new NullPointerException("Parameter id is null");
        if("".equals(id)) throw new IllegalArgumentException("Parameter id has empty value");
        //
        return cache.remove(id) != null;
    }

    /**
     * Clears the cache.
     */
    public synchronized void clear() {
        cache.clear();
    }

    //--------------------------------------------------------------------

    /**
     * Returns string representation of cache.
     * <p>Typically returns cache statistics.
     * @return info string.
     */
    public String toString() {
        return "ObjectCache{" +
               "timeout=" + timeout +
               ", items count: " + cache.size() +
               '}';
    }

    /**
     * Returns current count of chached items.
     * @return count of chached items.
     */
    public synchronized int getItemsCount(){
        return cache.size();
    }

    /**
     * Returns approximated cache size in bytes.
     * <p>Note: Cache size is count from items toString length. So the size is
     * only approximated and strongly depends on item.toString implementation.
     * @return approximated cache size in bytes.
     */
    public synchronized long getCacheSize() {
        return cache.toString().getBytes().length;
    }

}
